﻿using System;
using System.Collections.Generic;
using System.Text;
using Statistics;//блок статистической обработки

using ELW.Library.Math;
using ELW.Library.Math.Exceptions;
using ELW.Library.Math.Expressions;
using ELW.Library.Math.Tools;

namespace Simulator
{
    /// <summary>
    /// Блок действия
    /// </summary>
    [Serializable]
    public class Action: Block, IActionData
    {
        //занят ли блок
        private bool busy = false;
        
        //Список ресурсов, к которым подключен блок
        private List<Resource> resources = new List<Resource>();
        /// <summary>
        /// Получить список ресурсов
        /// </summary>
        public List<Resource> Resources { get { return resources; } }

        //очередь сущностей, поступающих в блок
        private Queue<Entity> que = new Queue<Entity>();
        /// <summary>
        /// Веса (важности) параметров ресурсов, подключенных к блоку
        /// </summary>
        public List<double> WeightsP { get; set; }
        /// <summary>
        /// Веса (важности) атомарных ресурсов, подключенных к блоку
        /// </summary>
        public List<double> WeightsR { get; set; }

        /// <summary>
        /// Единицы измерения времени
        /// </summary>
        public TimeUnits SimulationTimeUnits { get; set; }
    
        //скомпилированные выражения для парсера
        public CompiledExpression TimeCompiledString;
        public CompiledExpression QualityCompiledString;


        private string timeString, qualityString;
        /// <summary>
        /// Строка для выражения времени
        /// </summary>
        public string TimeString
        {
            get
            {
                return timeString;
            }
            set
            {
                timeString = value;
                PreparedExpression preparedExpression = ToolsHelper.Parser.Parse(timeString);
                TimeCompiledString = ToolsHelper.Compiler.Compile(preparedExpression);
            }
        }
        /// <summary>
        /// Строка для выражения качества
        /// </summary>
        public string QualityString
        {
            get
            {
                return qualityString;
            }
            set
            {
                qualityString = value;
                PreparedExpression preparedExpression = ToolsHelper.Parser.Parse(qualityString);
                QualityCompiledString = ToolsHelper.Compiler.Compile(preparedExpression);
            }
        }
 

        /// <summary>
        /// Занят ли блок действия (есть ли свободные ресурсы)?
        /// </summary>
        public bool Busy
        {
            get { return busy; }
        }

        /// <summary>
        /// Положить сущность в блок
        /// </summary>
        /// <param name="e">Cущность, которую кладем</param>
        public override void PutEntity(ref Entity e)
        {
            e.OwnerBlock = this;
            if (busy)//если занят - то положим в очередь
            {
                e.TimeRemained = double.MaxValue;//чтобы ждала, пока ее возьмут, хе-хе!
                que.Enqueue(e);
            }
            else
            {
                //если не занят, рассчитаем время до конца процедуры и качество на выходе
                Calculate(ref e);
                //займем ресурсы
                AllocateRes(e);
            }
        }
        /// <summary>
        /// Передать сущность дальше
        /// </summary>
        /// <param name="e">Сущность, которую передаем</param>
        public void TransferEntity(ref Entity e)//передать сущность дальше
        {
            //освободим ресурсы
            FreeEntity(e);
            //передадим сущность далее
            Next.PutEntity(ref e);
            //проверим очередь
            if (que.Count > 0)
            {
                Entity newE = que.Dequeue();
                Calculate(ref newE);
                AllocateRes(newE);
            }
        }
        //освободить ресурсы при покидании сущности e
        private void FreeEntity(Entity e)
        {
            foreach (Resource res in resources)
            {
                res.FreeEntity(e);
            }
            busy = false;//если блок был занят, то теперь свободен, т.к. есть свободные ресурсы
        }

        /// <summary>
        /// Рассчитать для сущности ее задержку и качество на выходе
        /// </summary>
        private void Calculate(ref Entity e)
        {
            //старый вариант без парсера
            //e.TimeRemained = StatisticMath.Calculate(DistribT, CoefsT, e.Quality, e.Complexity);
            //e.Quality = (int)StatisticMath.Calculate(DistribQ, CoefsQ, e.Quality, e.Complexity);

            //Воспользуемся парсером

            List<VariableValue> variables = new List<VariableValue>();
            double C = e.Complexity ;
            variables.Add(new VariableValue(C, "C"));

            double Q = e.Quality;
            variables.Add(new VariableValue(Q, "Q"));

            double P = CalcParam();
            variables.Add(new VariableValue(P, "P"));

            double time = ToolsHelper.Calculator.Calculate(TimeCompiledString, variables);
            //переведем time в секунды
            double d = 1;//множитель
            switch (SimulationTimeUnits)
            {
                case TimeUnits.days:
                    d = 3600 * 8;//секунд в рабочем дне (8 ч)
                    break;
                case TimeUnits.hours:
                    d = 3600;
                    break;
                case TimeUnits.minutes:
                    d = 60;
                    break;
            }
            e.TimeRemained = d * time;
            
            e.Quality = ToolsHelper.Calculator.Calculate(QualityCompiledString, variables);
        }
        /// <summary>
        /// Рассчитать приведенное качество ресурсов по формуле sum{i,j}(w[i]*v[j]*q[i][j]). Веса могут быть не нормированы
        /// </summary>
        /// <returns>Приведенное качество</returns>
        public double CalcParam()
        {
            double SWQ=0, SWP=0;//суммы весов
            for (int j = 0; j < WeightsP.Count; j++)
            {//по качествам
                SWQ += WeightsP[j];
            }
            double Qual = 0 , buf;
            
            for(int i = 0; i< resources.Count; i++)
            {//по ресурсам
                buf = 0;
                for(int j=0;j< WeightsP.Count; j++)
                {//по качествам
                    foreach (AtomicRes ar in resources[i].Atoms)
                    {
                        buf += WeightsP[j] * ar.Params[j];
                    }
                    
                }
                if(SWQ == 1.0)
                    Qual += WeightsR[i]*buf;
                else
                    Qual += WeightsR[i] * buf/SWQ;
                SWP += WeightsR[i];
            }
            
            return (SWP == 1.0 ? Qual : Qual/ SWQ);
        }

        //занять свободные ресурсы сущностью e
        private void AllocateRes(Entity e)
        {
            busy = false;
            for (int i = 0; i < resources.Count; i++)
            {//все ресурсы свободны

                if (resources[i].AllBusy)//если хоть один ресурс полностью занят, то блок действия занят
                {
                    busy = true;
                    return;
                }
                resources[i].PutEntity(ref e);//если не занят, то займем сущностью
                if (resources[i].AllBusy)//если он полностью занят, то блок действия занят
                    busy = true;
            }
            
        }
        
        /// <summary>
        /// Подключить ресурс к блоку Action
        /// </summary>
        /// <param name="r">Ресурс</param>
        public void ConnectResource(Resource r)
        {
            resources.Add(r);
            if (resources.Count == 1)
                WeightsR.Add(1);
            else
                WeightsR.Add(0);//заполним веса предварительно нулями
        }
        /// <summary>
        /// Отключить ресурс от блока Action
        /// </summary>
        /// <param name="r">Ресурс</param>
        public void DisconnectResource(Resource r)
        {
            int i = resources.FindIndex(res => res == r);
            if (i != -1)
            {
                resources.Remove(r);
                WeightsR.RemoveAt(i);
            }

        }
        public void ClearResources()
        {
            resources.Clear();
            WeightsR.Clear();
        }
        /// <summary>
        /// Конструктор
        /// </summary>
        /// <param name="k">количество параметров атомарного ресурса</param>
        /// <param name="m">максимальное количество коэффициентов мат. выражения</param>
        public Action(int k, int m)
        {
            //заполним вектор важности (весов) параметров атомарного ресурса
            WeightsP = new List<double>();
            WeightsP.Add(1);
            for (int i = 1; i < k; i++)
            {
                WeightsP.Add(0);
            }

            WeightsR = new List<double>();

            SimulationTimeUnits = TimeUnits.seconds;
            //настройка параметров времени
            timeString = "10";
            qualityString = "5";
            TimeCompiledString = new CompiledExpression(new List<CompiledExpressionItem>());
            QualityCompiledString = new CompiledExpression(new List<CompiledExpressionItem>());
            TimeCompiledString.CompiledExpressionItems.Add(new CompiledExpressionItem(CompiledExpressionItemKind.Constant, 10.0));
            QualityCompiledString.CompiledExpressionItems.Add(new CompiledExpressionItem(CompiledExpressionItemKind.Constant, 5.0));
        }

        /// <summary>
        /// Конструктор
        /// </summary>
        public Action()
        {
            WeightsP = new List<double>();
            WeightsR = new List<double>();

            SimulationTimeUnits = TimeUnits.seconds;
            //заполним коэф-ты нулями
            //настройка параметров времени
            timeString = "10";
            qualityString = "5";
            TimeCompiledString = new CompiledExpression(new List<CompiledExpressionItem>());
            QualityCompiledString = new CompiledExpression(new List<CompiledExpressionItem>());
            TimeCompiledString.CompiledExpressionItems.Add(new CompiledExpressionItem(CompiledExpressionItemKind.Constant, 10.0));
            QualityCompiledString.CompiledExpressionItems.Add(new CompiledExpressionItem(CompiledExpressionItemKind.Constant, 5.0));
        }
        /// <summary>
        /// Привести поля экземпляра класса в соответствии с полями ad
        /// </summary>
        /// <param name="ad">Элемент, содержащий копируемые значения полей</param>
        public void UpdateByActionData(ActionData ad)
        {
            Name = ad.Name;
        }
        public override void ClearEntities()
        {
            que.Clear();
            busy = false;
        }

    }
}
